//
//  TCUBag.m
//  TreeMenuTest
//
//  Created by Etienne on 11/07/07.
//  Copyright (c) 2007 Etienne Samson. All rights reserved.
//

#import "TCUCFBag.h"

#import "CFRuntime_internal.h"

#import "TCUBag.h"

#include <objc/objc.h>

const void *CustomRetainCallBack (CFAllocatorRef allocator, const void *value) {
    return CFRetain(value);
}

void CustomReleaseCallBack (CFAllocatorRef allocator, const void *value) {
    CFRelease(value);
}

@implementation TCUBag
#pragma mark -
#pragma mark Custom Toll-Free Bridging
- (CFTypeID) _cfTypeID {
    return CFBagGetTypeID();
}

+ (void) load
{
    _CFRuntimeInitializeClassForBridging(CFBagGetTypeID());
    if (_CFRuntimeSetupBridging(CFBagGetTypeID(), [TCUCFBag class], [TCUCFBag__ class]))
        NSLog(@"Successfully setup bridge for TCUCFBag...");
    else
        NSLog(@"Failed _CFRuntimeSetupBridging()");
}

#pragma mark -
#pragma mark Memory Management
+ (id) bag {
    return [[[TCUCFBag alloc] init] autorelease];
}

+ (id) bagWithArray:(NSArray*)array
{
    return [[[TCUBag alloc] initWithArray:array] autorelease];
}

+ (id) bagWithSet:(NSSet*)set
{
    return [[[TCUBag alloc] initWithSet:set] autorelease];
}

+ (id) bagWithObjects:(id)object1, ...
{
    va_list args;
    va_start(args, object1);
    id object = [[[TCUBag alloc] initWithObjects:object1 arguments:args] autorelease];
    va_end(args);
    return object;
}


- (id) initWithObjects:(void*)objects count:(int)count
{
    CFBagCallBacks callbacks;
    
    callbacks.version = 0;
    callbacks.retain = CustomRetainCallBack;
    callbacks.release = CustomReleaseCallBack;
    callbacks.copyDescription = CFCopyDescription;
    callbacks.equal = CFEqual;
    callbacks.hash = CFHash;

    self = (TCUBag*)CFBagCreate(kCFAllocatorDefault, objects, count, &callbacks);
    return self;
}

- (id) init
{
    [self dealloc];
    return nil;
}

- (id) initWithSet:(NSSet*)set
{
    return [self initWithArray:[set allObjects]];
}

- (id) initWithObjects:(id)object1 arguments:(va_list)args
{
    id object;
    NSMutableArray *array = [NSMutableArray arrayWithObject:object1];
    while ((object = va_arg(args, id)))
        [array addObject:object];
    
    return [self initWithArray:array];
}

- (id) initWithObjects:(id)object1, ...
{
    va_list args;
    
    va_start(args, object1);
    [self initWithObjects:object1 arguments:args];
    va_end(args);
    return self;
}

- (id) initWithArray:(NSArray*)array
{
    Class* memory = calloc([array count], sizeof(Class));
    int i;
    for (i = 0; i < [array count]; i++)
        memory[i] = (id)[array objectAtIndex:i];
    
    return [self initWithObjects:memory count:[array count]];
}

#pragma mark -
#pragma mark Protocols
- (id) copyWithZone:(NSZone*)zone
{
    return (TCUCFBag*)CFBagCreateCopy(NULL,(CFBagRef)self);
}

- (id) mutableCopyWithZone:(NSZone*)zone
{
    return (TCUCFBag*)CFBagCreateMutableCopy(NULL, 0, (CFBagRef)self);
}

- (id) initWithCoder:(NSCoder*)coder
{
    NSArray *array = [coder decodeObjectForKey:@"objects"];
    return [self initWithArray:array];
}

- (void) encodeWithCoder:(NSCoder*)coder
{
    [coder encodeObject:[self allObjects] forKey:@"objects"];
}

#pragma mark -
#pragma mark Core Foundation API
- (int) count
{
    return CFBagGetCount((CFBagRef)self);
}

- (int) countForObject:(id)object
{
    return CFBagGetCountOfValue((CFBagRef)self, object);
}

- (BOOL) containsObject:(id)value
{
    return CFBagGetValueIfPresent((CFBagRef)self, value, NULL);
}

- (id) member:(id)value
{
    const void* returned = CFBagGetValue((CFBagRef)self, (void*)value);
    return (returned != NULL ? (id)returned : nil);
}

- (NSArray*) allObjects
{
    NSMutableArray *array = [NSMutableArray array];
    Class* memory = calloc([self count], sizeof(Class));
    CFBagGetValues((CFBagRef)self, (void*)memory);

    int i;
    for (i = 0; i < [self count] ; i++)
        [array addObject:memory[i]];
    
    return array;
}

void makeObjectPerformSelectorFunction(const void *value, void *context)
{
    NSDictionary *dict = (NSDictionary*)context;
    
    SEL selector = NSSelectorFromString([dict valueForKey:@"selector"]);
    id object = [dict valueForKey:@"object"];
    
    if (!selector)
        [NSException raise:NSInvalidArgumentException format:@"selector can't be nil"];
    
    if (object)
        objc_msgSend((id)value, context, object);
    else
        objc_msgSend((id)value, context);
}

- (void) makeObjectsPerformSelector:(SEL)selector
{
    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
        NSStringFromSelector(selector), @"selector",
        nil];
    
    CFBagApplyFunction((CFBagRef)self, makeObjectPerformSelectorFunction, dict);
}

- (void) makeObjectsPerformSelector:(SEL)selector withObject:(id)object
{
    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
        NSStringFromSelector(selector), @"selector",
        object,                         @"object",
        nil];
    
    CFBagApplyFunction((CFBagRef)self, makeObjectPerformSelectorFunction, dict);
}

#pragma mark -
#pragma mark API Additions
- (NSEnumerator*) objectEnumerator
{
    return [[self allObjects] objectEnumerator];
}

/*#warning FIXME Tests shows there is an issue with how data is ordered inside us...
- (BOOL) isEqualToBag:(TCUBag*)otherBag
{
    return [self isEqual:otherBag];
}

- (BOOL) isEqual:(id)otherObject
{
    return YES;
}*/
@end
